﻿using System;
using System.Linq;
using System.Threading;
using Pfz.Collections;
using Pfz.Threading;
using System.Net;

namespace Pfz.Remoting
{
	/// <summary>
	/// Class that acts as a server to remoting connections.
	/// </summary>
	public class RemotingServer:
		RemotingCommon
	{
		#region Fields
			private readonly IChannellerListener _listener;
			private AutoTrimDictionary<long, RemotingClient> _clients = new AutoTrimDictionary<long, RemotingClient>();
			private long _idGenerator;
		#endregion

		#region Constructors
			/// <summary>
			/// Creates the server using the given parameters.
			/// </summary>
			public RemotingServer(IChannellerListener listener)
			{
				if (listener == null)
					throw new ArgumentNullException("listener");

				_listener = listener;
			}

			/// <summary>
			/// Creates a new RemotingServer object, that will listen at the given tcp/ip port.
			/// </summary>
			public RemotingServer(int tcpIpPort)
			{
				//_listener = new HybridChannellerListener(tcpIpPort, RemotingCommon.DefaultBufferSizePerChannel);
				_listener = new ChannellerListener(IPAddress.Any, tcpIpPort, RemotingCommon.DefaultBufferSizePerChannel); 
			}
		#endregion
		#region Dispose
			/// <summary>
			/// Closes the server connection and all client connections.
			/// </summary>
			protected override void Dispose(bool disposing)
			{
				if (disposing)
				{
					var clients = _clients;
					if (clients != null)
					{
						_clients = null;
						foreach(var client in clients.Values)
						{
							client.Disposed -= _ClientDisposed;
							client.Dispose();
						}
						clients.Dispose();
					}

					var listener = _listener;
					if (listener != null)
						listener.Dispose();
				}

				base.Dispose(disposing);
			}
		#endregion

		#region Methods
			#region _ClientDisposed
				private void _ClientDisposed(object sender, EventArgs args)
				{
					RemotingClient client = (RemotingClient)sender;
					try
					{
						_clients.Remove(client._id);
					}
					catch
					{
						if (!WasDisposed)
							throw;
					}
				}
			#endregion

			#region Start
				/// <summary>
				/// Starts the server and makes its parameters read-only.
				/// </summary>
				public void Start()
				{
					CheckThread();

					if (_parameters._isReadOnly)
						throw new RemotingException("The RemotingServer is already running.");

					UnlimitedThreadPool.Run
					(
						() =>
						{
							try
							{
								while(!WasDisposed)
								{
									var channeller = _listener.TryAccept();
									if (channeller == null)
										return;

									try
									{
										UnlimitedThreadPool.Run
										(
											() =>
											{
												RemotingClient client = null;

												long id = Interlocked.Increment(ref _idGenerator);

												try
												{
													client = CreateClient(_parameters);
													client._id = id;
													client.Start(channeller);
												}
												catch
												{
													if (client != null)
														client.Dispose();

													channeller.Dispose();

													return;
												}

												if (WasDisposed)
													return;

												_clients.Add(id, client);

												var clientConnected = ClientConnected;
												if (clientConnected != null)
												{
													var args = new RemotingClientConnectedEventArgs();
													args.Client = client;
													clientConnected(this, args);
												}
											}
										);
									}
									catch
									{
										channeller.Dispose();
										throw;
									}
								}
							}
							catch
							{
								if (!WasDisposed)
									throw;
							}
						}
					);
				}
			#endregion
			#region CreateClient
				/// <summary>
				/// Executed when a client connects, so a "RemotingClient" object must be created.
				/// This method is here in case you need to return a more specific remoting client.
				/// 
				/// You must create the RemotingClient using the version that receives the the "parameters".
				/// </summary>
				protected virtual RemotingClient CreateClient(RemotingParameters parameters)
				{
					var result = new RemotingClient(parameters);
					return result;
				}
			#endregion

			#region GetConnectedClients
				/// <summary>
				/// Gets an array with all connected clients.
				/// </summary>
				public RemotingClient[] GetConnectedClients()
				{
					return _clients.Values.ToArray();
				}
			#endregion
		#endregion
		#region Events
			#region ClientConnected
				/// <summary>
				/// Event invoked when just after a client connects.
				/// </summary>
				public event EventHandler<RemotingClientConnectedEventArgs> ClientConnected;
			#endregion
		#endregion
	}
}
